Support the Kitty keyboard protocol (flag 1, disambiguate)#2067
Support the Kitty keyboard protocol (flag 1, disambiguate)#2067Carreau wants to merge 3 commits intoprompt-toolkit:mainfrom
Conversation
|
Alternate to #2066, that push it (much further) using kitty, draft for now, I need to check some things, and trim it down as it only support part of the kitty protocol; I need to make sure of a few things (whether it's set or push/pop; and how it behaves in some case) and re-read everything |
Push the Kitty keyboard protocol on startup and pop it on exit so supporting terminals deliver modified keys — Ctrl-Enter, Shift-Enter, Ctrl-Shift-Enter, Alt-letter, and full modifier coverage on the navigation block — as distinct CSI u sequences instead of being collapsed into their unmodified equivalents. Terminals that don't implement the protocol silently ignore the push, so there is no regression for them. Detection mirrors the existing CPR machinery: the renderer emits `CSI ? u`, a new binding under key_binding/bindings/ consumes the response and flips `Renderer.kitty_support` from UNKNOWN to SUPPORTED, letting callers branch on capability if they want. The bulk of the code lives in new files: - input/kitty_keyboard.py — CSI u decoder, functional-key table, query-response parser - output/kitty_keyboard.py — push/pop context manager with reference-counted depth, sequence constants - key_binding/bindings/kitty_keyboard.py — response-consuming binding - docs/pages/advanced_topics/kitty_keyboard_protocol.rst — maintainer notes on wire format, capability detection, and known sharp edges Touch-points in existing code are small and mirror the CPR pattern: one import + one regex + one dispatch branch in vt100_parser.py, a push/query/pop trio in renderer.py, one binding registration in key_binding/defaults.py, four new enum values in keys.py. prompt_toolkit does not push xterm's modifyOtherKeys. Users whose terminal or tmux has it enabled independently still get the existing `CSI 27` Enter fallback in ansi_escape_sequences.py, which folds all three modifier+Enter variants to plain Keys.ControlM so the form at least submits rather than silently doing nothing. New Keys: ControlEnter, ControlShiftEnter, ShiftEnter, KittyKeyboardResponse. Bindings registered as `c-enter` / `s-enter` / `c-s-enter` fire on Kitty-capable terminals; on non-Kitty terminals they don't fire (plain Enter fires instead, as before).
|
Hi Carreau, Jonathan, I have also implemented support for CSI-u / kitty keyboard protocol in I generated new entries for I also added The |
|
Ah, that is interesting! i will have a look! |
|
I'd love to know if @jonathanslenders would be open to having this in core anyway; otherwise I'll if I can adopt a method similar to yours in IPython (with credit). |
|
Just nudging @jonathanslenders to know if this is something I should be working on, or trying to work around in IPython. |
|
Yes! I'm open to supporting the Kitty keyboard protocol. I have not had the time yet to review this due to lack of time. This is quite a big PR. (Is this an AI contribution btw? Just asking.) |
|
Claude helped yes; i wanted to make sure you were ok for support; if it's not ok to have AI help I can completely redo it by hand. But i wanted to know if you were ok with kitty in prompt toolkit before pushing on it. Take your time to focus on other things; i'll mark as ready for review once i actually want a review :-) |
|
Claude is fine as long as the code is properly reviewed. Also, I'm definitely fine with the Kitty protocol, it was even on my wish list of things I'd love to have. |
| kitty, ghostty, wezterm, foot. | ||
| - **Alt vs. Esc-prefix.** Kitty reports Alt as a modifier; the legacy | ||
| path reports it as ``(Esc, letter)``. The decoder emits | ||
| ``(Keys.Escape, base_key)`` for Alt-prefixed keys to match legacy |
There was a problem hiding this comment.
This is fine btw. It's similar to how things work on Windows.
| # Lazily install counter + flags state on the Output. We do it here | ||
| # rather than on Output.__init__ so this module stays fully | ||
| # self-contained and the rest of the codebase has no mention of it. | ||
| depth: int = getattr(output, "_kitty_keyboard_depth", 0) |
There was a problem hiding this comment.
In general, I avoid any usage of getattr/AttributeError/setattr/hasattr. This does not allow for type checking.
Better use polymorphism, and ensure that the Output interface has methods for anything we need. Don't assume attributes on an interface, only methods.
| # a no-op when nothing is registered — no null-check needed — and | ||
| # an exception during `__enter__` is surfaced by the stack itself | ||
| # rather than leaving a half-entered CM on the renderer. | ||
| self._kitty_keyboard_stack: ExitStack = ExitStack() |
There was a problem hiding this comment.
I'm not a big fan of using ExitStack in a way where it's not being used as a context manager, because there's no guarantee that the stack will be closed.
However, the right approach (if we need ExitStack) would be to add a classmethod constructor to Renderer which behaves as a contextmanager, and as a result of that, we'd need to do the same for Application. This is not feasible without breaking APIs.
Maybe my thinking here is a result of my focus on structured concurrency over the past 5 years or so.
I need to take some time to review in more detail, but it might be better to not implement kitty_keyboard_protocol as a contextmanager, but as distinct methods on the Output implementation:
Output.enable_kitty_protocol()andOutput.disable_kitty_protocol()
The Vt100Output implementation can then have a field _kitty_keyboard_depth and keep track of that.
| # every render burns a round-trip and has no value: the | ||
| # underlying terminal doesn't change capability under us. | ||
| if self.kitty_support is KittySupport.UNKNOWN: | ||
| self.output.write_raw(KITTY_QUERY) |
There was a problem hiding this comment.
Better add a method Output.ask_for_kitty_support() similar to Output.ask_for_cpr(). Output implementations that don't support Kitty don't need to implement that. (empty method in base class is fine).
This renderer class should not be concerned with specific Vt100 escape sequences like KITTY_QUERY.
Push the Kitty keyboard protocol on startup and pop it on exit so
supporting terminals deliver modified keys — Ctrl-Enter, Shift-Enter,
Ctrl-Shift-Enter, Alt-letter, and full modifier coverage on the
navigation block — as distinct CSI u sequences instead of being
collapsed into their unmodified equivalents. Terminals that don't
implement the protocol silently ignore the push, so there is no
regression for them.
Detection mirrors the existing CPR machinery: the renderer emits
CSI ? u, a new binding under key_binding/bindings/ consumes theresponse and flips
Renderer.kitty_supportfrom UNKNOWN to SUPPORTED,letting callers branch on capability if they want.
The bulk of the code lives in new files:
query-response parser
reference-counted depth, sequence constants
notes on wire format, capability detection, and known sharp edges
Touch-points in existing code are small and mirror the CPR pattern:
one import + one regex + one dispatch branch in vt100_parser.py, a
push/query/pop trio in renderer.py, one binding registration in
key_binding/defaults.py, four new enum values in keys.py.
prompt_toolkit does not push xterm's modifyOtherKeys. Users whose
terminal or tmux has it enabled independently still get the existing
CSI 27Enter fallback in ansi_escape_sequences.py, which folds allthree modifier+Enter variants to plain Keys.ControlM so the form at
least submits rather than silently doing nothing.
New Keys: ControlEnter, ControlShiftEnter, ShiftEnter,
KittyKeyboardResponse. Bindings registered as
c-enter/s-enter/c-s-enterfire on Kitty-capable terminals; on non-Kitty terminalsthey don't fire (plain Enter fires instead, as before).